// David Eberly, Geometric Tools, Redmond WA 98052
// Copyright (c) 1998-2025
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
// File Version: 8.0.2025.05.10

#include "GenerateMeshUVsWindow3.h"
#include <Applications/WICFileIO.h>
#include <Graphics/MeshFactory.h>
#include <Graphics/Texture2Effect.h>
#include <Mathematics/ArbitraryPrecision.h>
#include <Mathematics/PlanarMesh.h>
#include <iostream>
#include <random>

#if defined(GENERATE_MESH_UVS_GPU)
#include <MathematicsGPU/GPUGenerateMeshUV.h>
#else
#include <Mathematics/GenerateMeshUV.h>
#endif

GenerateMeshUVsWindow3::GenerateMeshUVsWindow3(Parameters& parameters)
    :
    Window3(parameters),
    mDrawMeshOriginal(true)
{
    if (!SetEnvironment())
    {
        parameters.created = false;
        return;
    }

    mNoCullState = std::make_shared<RasterizerState>();
    mNoCullState->cull = RasterizerState::Cull::NONE;
    mNoCullWireState = std::make_shared<RasterizerState>();
    mNoCullWireState->fill = RasterizerState::Fill::WIREFRAME;
    mNoCullWireState->cull = RasterizerState::Cull::NONE;
    mEngine->SetRasterizerState(mNoCullState);

    CreateScene();
    InitializeCamera(60.0f, GetAspectRatio(), 0.1f, 1000.0f, 0.001f, 0.001f,
        { -3.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f });
    mPVWMatrices.Update();
}

void GenerateMeshUVsWindow3::OnIdle()
{
    if (mCameraRig.Move())
    {
        mPVWMatrices.Update();
    }

    mEngine->ClearBuffers();
    if (mDrawMeshOriginal)
    {
        mEngine->Draw(mMeshOriginal);
    }
    else
    {
        mEngine->Draw(mMeshResampled);
    }
    mEngine->DisplayColorBuffer(0);
}

bool GenerateMeshUVsWindow3::OnCharPress(uint8_t key, int32_t x, int32_t y)
{
    switch (key)
    {
    case 'w':
    case 'W':
        if (mNoCullState == mEngine->GetRasterizerState())
        {
            mEngine->SetRasterizerState(mNoCullWireState);
        }
        else
        {
            mEngine->SetRasterizerState(mNoCullState);
        }
        return true;
    case 'm':
    case 'M':
        mDrawMeshOriginal = !mDrawMeshOriginal;
        return true;
    }
    return Window3::OnCharPress(key, x, y);
}

bool GenerateMeshUVsWindow3::SetEnvironment()
{
    std::string path = GetGTEPath();
    if (path == "")
    {
        return false;
    }

    mEnvironment.Insert(path + "/Samples/Data/");

    if (mEnvironment.GetPath("MedicineBag.png") == "")
    {
        LogError("Cannot find file MedicineBag.png.");
        return false;
    }

    return true;
}

void GenerateMeshUVsWindow3::CreateScene()
{
    CreateMeshOriginal();
    CreateMeshResampled();
    mTrackBall.Update();
}

void GenerateMeshUVsWindow3::CreateMeshOriginal()
{
    std::mt19937 mte;
    std::uniform_real_distribution<float> rnd(-1.0f, 1.0f);

    // Generate a perturbed hemisphere.
    VertexFormat vformat;
    vformat.Bind(VASemantic::POSITION, DF_R32G32B32_FLOAT, 0);
    vformat.Bind(VASemantic::TEXCOORD, DF_R32G32_FLOAT, 0);
    MeshFactory mf;
    mf.SetVertexFormat(vformat);
    mMeshOriginal = mf.CreateDisk(16, 16, 1.0f);
    float height = 0.25f;
    float radius = std::sqrt(1.0f - height*height);
    auto const& vbuffer = mMeshOriginal->GetVertexBuffer();
    uint32_t numVertices = vbuffer->GetNumElements();
    Vertex* vertices = vbuffer->Get<Vertex>();
    for (uint32_t i = 0; i < numVertices; ++i)
    {
        // Start with a hemisphere.
        float x = radius * vertices[i].position[0];
        float y = radius * vertices[i].position[1];
        float z = std::sqrt(std::max(1.0f - x*x - y*y, 0.0f));

        // Perturb points along rays, which preserves non-self-intersection.
        float r = 1.0f + 0.125f*rnd(mte);
        vertices[i].position = { r * x, r * y, r * z };
    }

    std::string path = mEnvironment.GetPath("MedicineBag.png");
    auto texture = WICFileIO::Load(path, false);
    auto effect = std::make_shared<Texture2Effect>(mProgramFactory, texture,
        SamplerState::Filter::MIN_L_MAG_L_MIP_P, SamplerState::Mode::CLAMP, SamplerState::Mode::CLAMP);
    mMeshOriginal->SetEffect(effect);

    mPVWMatrices.Subscribe(mMeshOriginal->worldTransform, effect->GetPVWMatrixConstant());

    mTrackBall.Attach(mMeshOriginal);
}

void GenerateMeshUVsWindow3::CreateMeshResampled()
{
    // If GPUFloat is 'double', the tcoords[] generated by the HLSL code
    // do not cause failures in pmesh.GetContainingTriangle(P, triangle).
    // The tcoords[] generated by the GLSL code do cause failures. Writing
    // the tcoords[] to text files using std::setprecision(17) for each
    // component, there are noticeable differences in the tcoords[] values.
    // If GPUFloat is 'float', the tcoords[] generated by the HLSL code and
    // by the GLSL code do not cause failures. However, the tcoords[]
    // values are noticeably different.
#if defined(GENERATE_MESH_UVS_GPU)
    using GPUFloat = float;
#else
    using GPUFloat = double;
#endif

    auto const& vbuffer = mMeshOriginal->GetVertexBuffer();
    uint32_t numVertices = vbuffer->GetNumElements();
    Vertex* vertices = vbuffer->Get<Vertex>();

    auto const& ibuffer = mMeshOriginal->GetIndexBuffer();
    int32_t numIndices = (int32_t)ibuffer->GetNumElements();
    int32_t const* indices = ibuffer->Get<int32_t>();
    std::vector<Vector3<GPUFloat>> dvertices(numVertices);
    for (uint32_t i = 0; i < numVertices; ++i)
    {
        for (int32_t j = 0; j < 3; ++j)
        {
            dvertices[i][j] = static_cast<GPUFloat>(vertices[i].position[j]);
        }
    }

    // Generate texture coordinates.
#if defined(GENERATE_MESH_UVS_CPU_SINGLE_THREADED)
    // Use the main application thread.
    uint32_t const numThreads = 0;
    GenerateMeshUV<GPUFloat> pm(numThreads);
#endif
#if defined(GENERATE_MESH_UVS_CPU_MULTITHREADED)
    // Use half the number of hardware threads on the CPU.
    uint32_t const numThreads = std::thread::hardware_concurrency() / 2;
    GenerateMeshUV<GPUFloat> pm(numThreads);
#endif
#if defined(GENERATE_MESH_UVS_GPU)
    // Use the GPU, whether DX11/HLSL or GL46/GLSL.
    GPUGenerateMeshUV<GPUFloat> pm(mEngine, mProgramFactory);
#endif
    std::vector<Vector2<GPUFloat>> tcoords(numVertices);
    uint32_t numGaussSeidelIterations = 128;
    pm(numGaussSeidelIterations, true, numVertices, &dvertices[0], numIndices,
        indices, &tcoords[0]);

    // Resample the mesh.
    typedef BSNumber<UIntegerAP32> Numeric;
    typedef BSRational<UIntegerAP32> Rational;
    int32_t numTriangles = numIndices / 3;
    PlanarMesh<GPUFloat, Numeric, Rational> pmesh(numVertices, &tcoords[0],
        numTriangles, &indices[0]);

    VertexFormat vformat;
    vformat.Bind(VASemantic::POSITION, DF_R32G32B32_FLOAT, 0);
    vformat.Bind(VASemantic::TEXCOORD, DF_R32G32_FLOAT, 0);
    MeshFactory mf;
    mf.SetVertexFormat(vformat);
    int32_t size = 64;
    GPUFloat dsize = static_cast<GPUFloat>(size);
    mMeshResampled = mf.CreateRectangle(size, size, 1.0f, 1.0f);
    vertices = mMeshResampled->GetVertexBuffer()->Get<Vertex>();

    Vector2<GPUFloat> P{ static_cast<GPUFloat>(0), static_cast<GPUFloat>(0) };
    int32_t triangle = 0;
    std::array<GPUFloat, 3> bary = { static_cast<GPUFloat>(0), static_cast<GPUFloat>(0), static_cast<GPUFloat>(0) };
    std::array<int32_t, 3> lookup = { 0, 0, 0 };
    for (int32_t y = 0; y < size; ++y)
    {
        P[1] = y / dsize;
        for (int32_t x = 0; x < size; ++x)
        {
            P[0] = x / dsize;
            triangle = pmesh.GetContainingTriangle(P, triangle);
            if (triangle >= 0)
            {
                pmesh.GetBarycentrics(triangle, P, bary);
                pmesh.GetIndices(triangle, lookup);
                Vector3<GPUFloat> resampled =
                    bary[0] * dvertices[lookup[0]] +
                    bary[1] * dvertices[lookup[1]] +
                    bary[2] * dvertices[lookup[2]];
                for (int32_t i = 0; i < 3; ++i)
                {
                    vertices[x + size * y].position[i] = static_cast<float>(resampled[i]);
                }
            }
            else
            {
                std::cout << "GetContainingTriangle failed at (" << x << "," << y << ")" << std::endl;
                triangle = 0;
                for (int32_t i = 0; i < 3; ++i)
                {
                    vertices[x + size*y].position[i] = 0.0f;
                }
            }
        }
    }

    std::string path = mEnvironment.GetPath("MedicineBag.png");
    auto texture = WICFileIO::Load(path, false);
    auto effect = std::make_shared<Texture2Effect>(mProgramFactory, texture,
        SamplerState::Filter::MIN_L_MAG_L_MIP_P, SamplerState::Mode::CLAMP,
        SamplerState::Mode::CLAMP);
    mMeshResampled->SetEffect(effect);

    mPVWMatrices.Subscribe(mMeshResampled->worldTransform,  effect->GetPVWMatrixConstant());
    mTrackBall.Attach(mMeshResampled);
}

